using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Text.RegularExpressions;
using System.Threading;

namespace CodePlex.Resourceful.Amazon.ECS
{
    public class ECSClient : QueryAPIClient
    {
        public static readonly string DefaultServiceHost = "ecs.amazonaws.com";
        private static readonly string Version = "2009-06-01";


        public ECSClient(string accessKeyID, string secretAccessKey, bool useSSL, string method)
            : base(accessKeyID, secretAccessKey, useSSL, method, DefaultServiceHost, Version) 
        {
             this.Throttle = TimeSpan.FromSeconds(1);
        }


        public ItemSearchResponse ItemSearch(string searchIndex, string keywords, int itemPage)
        {
            NameValuePairs nvp = new NameValuePairs();
            nvp["Service"] = "AWSECommerceService";
       

            nvp["Operation"] = "ItemSearch";

            nvp["SearchIndex"] = searchIndex;
            nvp["Keywords"] = keywords;
            nvp["ResponseGroup"] = "Large";
            nvp["ItemPage"] = itemPage.ToString();

            return ItemSearchResponse.Parse(this.DoQueryRequest("onca/xml", nvp).AsXmlReader());


        }


    }










































    public abstract class Base
    {

        private static string Clean(string value)
        {
            // char 160 (nbsp) -> char 32 (space)
            // postgres "UNICODE" encoding will accept this char
            // but won't restore onto "UTF8" encoding: invalid byte sequence for encoding "UTF8": 0xa0
            return value.Replace((char)160, ' ').Trim();
        }

        protected static NameValuePairs ParseNameValuePairs(XmlReader reader)
        {
            string parentElementName = reader.Name;
            NameValuePairs rt = new NameValuePairs();

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    string name = reader.Name;
                    string value = Clean(Utils.ReadString(reader));
                    rt[name] = value;
                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == parentElementName)
                {
                    return rt;
                }

            }

            throw CodePlexException.Format("Failed to parse NameValuePairs.");

        }

        protected static NameValuePairs ParseNameValuePairs(string childElementName, XmlReader reader)
    
         {
            string parentElementName = reader.Name;
            NameValuePairs rt = new NameValuePairs();

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name == childElementName)
                    {
                        string name = reader.GetAttribute("Name");
                        string value = reader.GetAttribute("Value");
                        rt[name] = value;
                    }
                    
                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == parentElementName)
                {
                    return rt;
                }

            }

            throw CodePlexException.Format("Failed to parse NameValuePairs.");
        }
    }



    public class BrowseNode : Base
    {
        private string _browseNodeID;
        private string _name;
        private BrowseNode _parent;


        public string BrowseNodeID { get { return _browseNodeID; } }
        public string Name { get { return _name; } }
        public BrowseNode Parent { get { return _parent; } }


        public string FullName { get { return this.GetFullName("/"); } }

        private string GetFullName(string delimiter)
        {
            if (this.Parent == null)
            {
                return this.Name;
            }
            return this.Parent.GetFullName(delimiter) + delimiter + this.Name;
        }




        internal static BrowseNode Parse(XmlReader reader)
        {

            BrowseNode rt = new BrowseNode();

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name == "BrowseNodeId")
                    {
                        rt._browseNodeID = Utils.ReadString(reader);
                    }
                    else if (reader.Name == "Name")
                    {
                        rt._name = Utils.ReadString(reader);
                    }
                    else if (reader.Name == "Ancestors")
                    {
                        rt._parent = ParseParent(reader);
                    }


                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "BrowseNode")
                {
                    return rt;
                }

            }

            throw CodePlexException.Format("Failed to parse BrowseNode.");
        }

        private static BrowseNode ParseParent(XmlReader reader)
        {
            BrowseNode rt = default(BrowseNode);

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name == "BrowseNode")
                    {
                        rt = BrowseNode.Parse(reader);
                    }
                    
                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "BrowseNode")
                {
                    return rt;
                }

            }

            throw CodePlexException.Format("Failed to parse BrowseNode.");
        }
    }


    public class Offers : Base
    {
        private int _totalOffers;
        private int _totalOfferPages;


        public int TotalOffers { get { return _totalOffers; } }
        public int TotalOfferPages { get { return _totalOfferPages; } }


        internal static Offers Parse(XmlReader reader)
        {


            Offers rt = new Offers();

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name == "TotalOffers")
                    {
                        rt._totalOffers = int.Parse(Utils.ReadString(reader));
                    }
                    else if (reader.Name == "TotalOfferPages")
                    {
                        rt._totalOfferPages = int.Parse(Utils.ReadString(reader));
                    }
                   

                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Offers")
                {
                    return rt;
                }

            }

            throw CodePlexException.Format("Failed to parse Offers.");
        }
    }


    public class Price : Base
    {
        private int _amount;
        private string _currencyCode;
        private string _formattedPrice;


        public int Amount { get { return _amount; } }
        public string CurrencyCode { get { return _currencyCode; } }
        public string FormattedPrice { get { return _formattedPrice; } }


        public override string ToString()
        {
            return string.Format("[{0} {1} {2}]", this.Amount, this.CurrencyCode, this.FormattedPrice);
        }
        internal static Price Parse(XmlReader reader)
        {
            string elementName = reader.Name;

            Price rt = new Price();

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name == "Amount")
                    {
                        rt._amount = int.Parse(Utils.ReadString(reader));
                    }
                    else if (reader.Name == "CurrencyCode")
                    {
                        rt._currencyCode = Utils.ReadString(reader);
                    }
                    else if (reader.Name == "FormattedPrice")
                    {
                        rt._formattedPrice = Utils.ReadString(reader);
                    }
                    
                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == elementName)
                {
                    return rt;
                }

            }

            throw CodePlexException.Format("Failed to parse Price.");
        }
    }


    public class OfferSummary : Base
    {
        private int _totalNew;
        private int _totalUsed;
        private int _totalCollectible;
        private int _totalRefurbished;
        private Price _lowestNewPrice;

        public int TotalNew { get { return _totalNew; } }
        public int TotalUsed { get { return _totalUsed; } }
        public int TotalCollectible { get { return _totalCollectible; } }
        public int TotalRefurbished { get { return _totalRefurbished; } }
        public Price LowestNewPrice { get { return _lowestNewPrice; } }


        internal static OfferSummary Parse(XmlReader reader)
        {
            OfferSummary rt = new OfferSummary();
          

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name == "TotalNew")
                    {
                        rt._totalNew = int.Parse(Utils.ReadString(reader));
                    }
                    else if (reader.Name == "TotalUsed")
                    {
                        rt._totalUsed = int.Parse(Utils.ReadString(reader));
                    }
                    else if (reader.Name == "TotalCollectible")
                    {
                        rt._totalCollectible = int.Parse(Utils.ReadString(reader));
                    }
                    else if (reader.Name == "TotalRefurbished")
                    {
                        rt._totalRefurbished = int.Parse(Utils.ReadString(reader));
                    }
                    else if (reader.Name == "LowestNewPrice")
                    {
                        rt._lowestNewPrice = Price.Parse(reader);
                    }
                   

                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "OfferSummary")
                {
                    return rt;
                }

            }

            throw CodePlexException.Format("Failed to parse OfferSummary.");
        }
    }


    public class ImageSet : Dictionary<string, Image>
    {
        private string _category;

        public string Category { get { return _category; } }


        internal static ImageSet Parse(XmlReader reader)
        {

            ImageSet rt = new ImageSet();
            rt._category = reader.GetAttribute("Category");

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {

                    string name = reader.Name;
                    Image image = Image.Parse(reader);

                    rt[name] = image;

                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "ImageSet")
                {
                    return rt;
                }

            }

            throw CodePlexException.Format("Failed to parse ImageSet.");
        }

    }

    public class Image : Base
    {
        private string _url;
        private int _heightPixels;
        private int _widthPixels;

        public string Url { get { return _url; } }
        public int HeightPixels { get { return _heightPixels; } }
        public int WidthPixels { get { return _widthPixels; } }


        public override string ToString()
        {
            return string.Format("[{0},{1}x{2}px]", this.Url, this.WidthPixels, this.HeightPixels);
        }



        internal static Image Parse(XmlReader reader)
        {

            string elementName = reader.Name;

            Image rt = new Image();


            Action<XmlReader> assertPixels = delegate(XmlReader item)
            {
                if (reader.GetAttribute("Units") != "pixels")
                {
                    throw CodePlexException.Format("Unknown Units: {0}", reader.GetAttribute("Units"));
                }
            };

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name == "URL")
                    {
                        rt._url = Utils.ReadString(reader);
                    }
                    else if (reader.Name == "Height")
                    {
                        assertPixels(reader);
                        rt._heightPixels = int.Parse(Utils.ReadString(reader));
                    }
                    else if (reader.Name == "Width")
                    {
                        assertPixels(reader);
                        rt._widthPixels = int.Parse(Utils.ReadString(reader));
                    }

                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == elementName)
                {
                    return rt;
                }

            }

            throw CodePlexException.Format("Failed to parse Image.");
        }


    }


    public class Error : Base
    {
        private readonly string _code;
        private readonly string _message;

        public Error(string code, string message)
        {
            _code = code;
            _message = message;
        }
        public string Code { get { return _code; } }
        public string Message { get { return _message; } }

    }

    public class Request : Base
    {
        private bool _isValid;
        private ReadOnlyList<Error> _errors;
        private NameValuePairs _itemSearchRequest;

        public bool IsValid { get { return _isValid; } }
        public ReadOnlyList<Error> Errors { get { return _errors; } }
        public NameValuePairs ItemSearchRequest { get { return _itemSearchRequest; } }



        private static ReadOnlyList<Error> ParseErrors(XmlReader reader)
        {
            List<Error> rt = new List<Error>();

            string code = default(string);

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name == "Code")
                    {
                        code = Utils.ReadString(reader);
                    }
                    if (reader.Name == "Message")
                    {
                        string message = Utils.ReadString(reader);
                        rt.Add(new Error(code, message));
                    }

                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Errors")
                {
                    return new ReadOnlyList<Error>(rt);
                }

            }

            throw CodePlexException.Format("Failed to parse Errors.");

        }

        internal static Request Parse(XmlReader reader)
        {
            Request rt = new Request();
            rt._errors = ReadOnlyList<Error>.Empty;

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name == "IsValid")
                    {
                        rt._isValid = bool.Parse(Utils.ReadString(reader));
                    }
                    else if (reader.Name == "Errors")
                    {
                        rt._errors = ParseErrors(reader);
                    }
                    else if (reader.Name == "ItemSearchRequest")
                    {
                        rt._itemSearchRequest = ParseNameValuePairs(reader);
                    }
                  

                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Request")
                {
                    return rt;
                }

            }

            throw CodePlexException.Format("Failed to parse Request.");
        }
    }

    public class Item : Base
    {

        private string _ASIN;
        private string _detailPageURL;
        private NameValuePairs _itemAttributes;
        private Image _smallImage;
        private Image _mediumImage;
        private Image _largeImage;
        private ReadOnlyList<ImageSet, string> _imageSets;
        private OfferSummary _offerSummary;
        private Offers _offers;
        private ReadOnlyList<BrowseNode> _browseNodes;

        public string ASIN { get { return _ASIN; } }
        public string DetailPageURL { get { return _detailPageURL; } }
        public NameValuePairs ItemAttributes { get { return _itemAttributes; } }
        public Image SmallImage { get { return _smallImage; } }
        public Image MediumImage { get { return _mediumImage; } }
        public Image LargeImage { get { return _largeImage; } }
        public ReadOnlyList<ImageSet, string> ImageSets { get { return _imageSets; } }
        public OfferSummary OfferSummary { get { return _offerSummary; } }
        public Offers Offers { get { return _offers; } }
        public ReadOnlyList<BrowseNode> BrowseNodes { get { return _browseNodes; } }


        public string ASINFromDetailPageURL
        {
            get
            {
                //http://www.amazon.com/Chemtrails/dp/B001B2NUIU%3FSubscriptionId%3D(amazonkey)%26tag%3Dws%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3DB001B2NUIU
                return Regex.Match(this.DetailPageURL, @"ASIN(=|\%3D)(\w+)").Groups[2].Value;
            }
        }
       


        internal static Item Parse(XmlReader reader)
        {
            Item rt = new Item();

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name == "ASIN")
                    {
                        rt._ASIN = Utils.ReadString(reader);
                    }
                    else if (reader.Name == "DetailPageURL")
                    {
                        rt._detailPageURL = Utils.ReadString(reader);
                    }
                    else if (reader.Name == "ItemAttributes")
                    {
                        rt._itemAttributes = ParseNameValuePairs(reader);
                    }
                    else if (reader.Name == "SmallImage")
                    {
                        rt._smallImage = Image.Parse(reader);
                    }
                    else if (reader.Name == "MediumImage")
                    {
                        rt._mediumImage = Image.Parse(reader);
                    }
                    else if (reader.Name == "LargeImage")
                    {
                        rt._largeImage = Image.Parse(reader);
                    }
                    else if (reader.Name == "ImageSets")
                    {
                        rt._imageSets = ParseImageSets(reader);
                    }
                    else if (reader.Name == "OfferSummary")
                    {
                        rt._offerSummary = OfferSummary.Parse(reader);
                    }
                    else if (reader.Name == "Offers")
                    {
                        rt._offers = Offers.Parse(reader);
                    }
                    else if (reader.Name == "BrowseNodes")
                    {
                        rt._browseNodes = ParseBrowseNodes(reader);
                    }
                   
                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Item")
                {
                    return rt;
                }

            }

            throw CodePlexException.Format("Failed to parse Item.");
        }

        private static ReadOnlyList<BrowseNode> ParseBrowseNodes(XmlReader reader)
        {
            List<BrowseNode> rt = new List<BrowseNode>();

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name == "BrowseNode")
                    {
                        rt.Add(BrowseNode.Parse(reader));
                    }
                   
                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "BrowseNodes")
                {
                    return new ReadOnlyList<BrowseNode>(rt);
                }

            }

            throw CodePlexException.Format("Failed to parse BrowseNodes.");
        }

        private static ReadOnlyList<ImageSet, string> ParseImageSets(XmlReader reader)
        {
            List<ImageSet> rt = new List<ImageSet>();


            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name == "ImageSet")
                    {
                        rt.Add(ImageSet.Parse(reader));
                    }
                   
                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "ImageSets")
                {
                    return new ReadOnlyList<ImageSet, string>(rt, delegate(ImageSet item)
                       {
                           return item.Category;
                       });
                }

            }

            throw CodePlexException.Format("Failed to parse ImageSets.");


           
        }
    }




    public class Items : ReadOnlyList<Item,string>
    {


        private int _totalResults;
        private int _totalPages;
        private Request _request;
        
        public int TotalResults { get { return _totalResults; } }
        public int TotalPages { get { return _totalPages; } }
        public Request Request { get { return _request; } }

        public Items(IEnumerable<Item> values, Converter<Item, string> indexFunction) : base(values, indexFunction) { }

        internal static Items Parse(XmlReader reader)
        {
            int totalResults = default(int);
            int totalPages = default(int);
            List<Item> items = new List<Item>();
            Request request = default(Request);

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name == "TotalResults")
                    {
                        totalResults = int.Parse(Utils.ReadString(reader));
                    }
                    else if (reader.Name == "TotalPages")
                    {
                        totalPages = int.Parse(Utils.ReadString(reader));
                    }
                    else if (reader.Name == "Item")
                    {
                        items.Add(Item.Parse(reader));
                    }
                    else if (reader.Name == "Request")
                    {
                        request = Request.Parse(reader);
                    }
                  
                  
                   

                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Items")
                {
                    Items rt = new Items(items, delegate(Item item)
                    {
                        return item.ASIN;
                    });
                    rt._totalPages = totalPages;
                    rt._totalResults = totalResults;
                    rt._request = request;
                    return rt;
                }

            }

            throw CodePlexException.Format("Failed to parse Items.");
        }
    }

    public class OperationRequest : Base
    {

        private NameValuePairs _httpHeaders;
        private NameValuePairs _arguments;
        private string _requestID;
        private double _requestProcessingTime;

        public string RequestID { get { return _requestID; } }
        public double RequestProcessingTime { get { return _requestProcessingTime; } }
        public NameValuePairs HttpHeaders { get { return _httpHeaders; } }
        public NameValuePairs Arguments { get { return _arguments; } }



         

        internal static OperationRequest Parse(XmlReader reader)
        {
            OperationRequest rt = new OperationRequest();

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name == "RequestId")
                    {
                        rt._requestID = Utils.ReadString(reader);
                    }
                    else if (reader.Name == "RequestProcessingTime")
                    {
                        rt._requestProcessingTime = double.Parse(Utils.ReadString(reader));
                    }
                    else if (reader.Name == "HTTPHeaders")
                    {
                        rt._httpHeaders = ParseNameValuePairs("Header", reader);
                    }
                    else if (reader.Name == "Arguments")
                    {
                        rt._arguments = ParseNameValuePairs("Argument", reader);
                    }

                }

                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "OperationRequest")
                {
                    return rt;
                }

            }

            throw CodePlexException.Format("Failed to parse OperationRequest.");
        }
    }

    public class ItemSearchResponse : Base
    {

        private OperationRequest _operationRequest;
        private Items _items;

        public OperationRequest OperationRequest { get { return _operationRequest; } }
        public Items Items { get { return _items; } }





        internal static ItemSearchResponse Parse(XmlReader reader)
        {
            ItemSearchResponse rt = new ItemSearchResponse();

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name == "OperationRequest")
                    {
                        rt._operationRequest = OperationRequest.Parse(reader);
                    }
                    else if (reader.Name == "Items")
                    {
                        rt._items = Items.Parse(reader);
                    }
                }
               

                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "ItemSearchResponse")
                {
                    return rt;
                }

            }

            throw CodePlexException.Format("Failed to parse ItemSearchResponse.");
        }

      
    }

}
